#include "MapRenderer.h"
#include "ScaleCalculator.h"
#include <TGS/MapControl2.0/MapToPixel.h>
#include <TGS/MapControl2.0/RendererList.h>
#include <TGS/MapControl2.0/Renderer.h>
#include <QPainter>
#include <cmath>
#include <algorithm>


namespace TGS{
namespace MapControl{


MapRenderer::MapRenderer()
	: _scaleCalculator(new ScaleCalculator())
	, _size(QSize(0, 0))
	, _outputUnits(Millimeters)
	, _renders(new RendererList())
{
}

MapRenderer::~MapRenderer()
{
	if(nullptr != _scaleCalculator)
	{
		delete _scaleCalculator;
		_scaleCalculator = nullptr;
	}

	if(nullptr != _renders && _renders->manageMemoryByMapControl())
	{
		delete _renders;
		_renders = nullptr;
	}
}


MapRenderer::Envelope MapRenderer::extent() const
{
	return _extent;
}

void MapRenderer::updateScale()
{
	_scale = _scaleCalculator->calculate( _extent, _size.width() );
}

bool MapRenderer::setExtent( const Envelope& extent )
{
	//remember the previous extent
	_lastExtent = _extent;

	// Don't allow zooms where the current extent is so small that it
	// can't be accurately represented using a double (which is what
	// currentExtent uses). Excluding 0 avoids a divide by zero and an
	// infinite loop when rendering to a new canvas. Excluding extents
	// greater than 1 avoids doing unnecessary calculations.

	// The scheme is to compare the width against the mean x coordinate
	// (and height against mean y coordinate) and only allow zooms where
	// the ratio indicates that there is more than about 12 significant
	// figures (there are about 16 significant figures in a double).

	if ( extent.getWidth() > 0 && extent.getWidth() < 1 && 
		 extent.getWidth() > 0 && extent.getWidth() < 1
		)
	{
		// Use abs() on the extent to avoid the case where the extent is
		// symmetrical about 0.
		double xMean = ( qAbs( extent.getMinX() ) + qAbs( extent.getMaxX() ) ) * 0.5;
		double yMean = ( qAbs( extent.getMinY() ) + qAbs( extent.getMaxY() ) ) * 0.5;

		double xRange = extent.getWidth() / xMean;
		double yRange = extent.getHeight() / yMean;

		static const double minProportion = 1e-12;
		if ( xRange < minProportion || yRange < minProportion )
			return false;
	}

	_extent = extent;
	if (!_extent.isEmpty()) 
	{
		adjustExtentToSize();
	}

	return true;
}



void MapRenderer::setOutputSize( QSize size, int dpi )
{
	_size = QSizeF( size.width(), size.height() );
	_scaleCalculator->setDpi( dpi );
	adjustExtentToSize();
}

void MapRenderer::setOutputSize( QSizeF size, double dpi )
{
	_size = size;
	_scaleCalculator->setDpi( dpi );
	adjustExtentToSize();
}

double MapRenderer::outputDpi()
{
	return _scaleCalculator->dpi();
}

QSize MapRenderer::outputSize()
{
	return _size.toSize();
}

QSizeF MapRenderer::outputSizeF()
{
	return _size;
}

void MapRenderer::adjustExtentToSize()
{
	double height = _size.height();
	double width = _size.width();

	MapToPixel newCoordXForm;

	if ( 0 == width || 0 == height )
	{
		_scale = 1;
		newCoordXForm.setParameters( 0, 0, 0, 0 );
		return;
	}

	// calculate the translation and scaling parameters
	// mapUnitsPerPixel = map units per pixel
	double mapUnitsPerPixelY = _extent.getHeight() / height;
	double mapUnitsPerPixelX = _extent.getWidth() / width;
	_mapUnitsPerPixel = mapUnitsPerPixelY > mapUnitsPerPixelX ? 
						mapUnitsPerPixelY : mapUnitsPerPixelX;

	// calculate the actual extent of the mapCanvas
	double minX, maxX, minY, maxY, whitespace;

	if ( mapUnitsPerPixelY > mapUnitsPerPixelX )
	{
		minY = _extent.getMinY();
		maxY = _extent.getMaxY();
		whitespace = (( width * _mapUnitsPerPixel ) - _extent.getWidth() ) * 0.5;
		minX = _extent.getMinX() - whitespace;
		maxX = _extent.getMaxX() + whitespace;
	}
	else
	{
		minX = _extent.getMinX();
		maxX = _extent.getMaxX();
		whitespace = (( height * _mapUnitsPerPixel ) - _extent.getHeight() ) * 0.5;
		minY = _extent.getMinY() - whitespace;
		maxY = _extent.getMaxY() + whitespace;
	}
	// update extent
	_extent.setMinX( minX );
	_extent.setMaxX( maxX );
	_extent.setMinY( minY );
	_extent.setMaxY( maxY );

	// update the scale
	updateScale();

	newCoordXForm.setParameters( _mapUnitsPerPixel, minX, minY, height );
	_renderContext.setMapToPixel( newCoordXForm );
	_renderContext.setExtent( _extent );
}


void MapRenderer::render( QPainter* painter, double* forceWidthScale )
{
	if ( _extent.isEmpty() ) return;
	if ( _size.width() == 1 && _size.height() == 1 ) return;

	_renderContext.setPainter( painter );
	//this flag is only for stopping during the current rendering progress,
	//so must be false at every new render operation
	_renderContext.setRenderingStopped( false );

	//calculate scale factor
	//use the specified dpi and not those from the paint device
	//because sometimes QPainter units are in a local coord sys (e.g. in case of QGraphicsScene)
	double sceneDpi = _scaleCalculator->dpi();
	double scaleFactor = 1.0;
	if ( _outputUnits == Millimeters )
	{
		if ( forceWidthScale )
		{
			scaleFactor = *forceWidthScale;
		}
		else
		{
			scaleFactor = sceneDpi / 25.4;
		}
	}
	
	_renderContext.setScaleFactor( scaleFactor );
	_renderContext.setRendererScale( _scale );
	_lastExtent = _extent;

	// render all layers in the stack, starting at the base
	_renders->render(&_renderContext);

}

void MapRenderer::addRenderer( Renderer* renderer )
{
	_renders->addRenderer(renderer);
}

void MapRenderer::removeRenderer( Renderer* renderer )
{
	_renders->removeRenderer(renderer);
}


TGS::GeoMath::Envelope MapRenderer::getFullExtent() const
{
	return _renders->getEnvelope();
}

void MapRenderer::clearRenderers()
{
	_renders->clear();
}

void MapRenderer::setSelectPolygon( const Polygon& selectPolygon )
{
	_renders->setSelectPolygon(selectPolygon);
}


void MapRenderer::setSelectPoint( const Point& selectPoint )
{
	_renders->setSelectPoint(selectPoint);
}


void MapRenderer::deselect()
{
	_renders->deselect();
}

void MapRenderer::setDrawPolygon( const Polygon& drawedPolygon )
{
	_renders->setDrawPolygon(drawedPolygon);
}


}//MapControl
}//TGS
